Skip to content

v0.3.0: Full Matlab parity — methods, examples, and decoding algorithms#55

Merged
iahncajigas merged 19 commits intomainfrom
feature/v0.3.0-missing-methods
Mar 11, 2026
Merged

v0.3.0: Full Matlab parity — methods, examples, and decoding algorithms#55
iahncajigas merged 19 commits intomainfrom
feature/v0.3.0-missing-methods

Conversation

@iahncajigas
Copy link
Copy Markdown
Contributor

@iahncajigas iahncajigas commented Mar 10, 2026

Summary

  • 484 Matlab methods audited across 16 classes — 466 ported with full behavioral parity (6 nominal gaps: Simulink CIF, Matlab-only disp/display)
  • All 5 paper examples rewritten as self-contained, documented scripts matching Matlab counterparts exactly (24 figures total)
  • ~5,600 lines of new decoding algorithms including SSGLM EM (PPSS_EMFB), UKF, KF_EM, PP_EM, mPPCO families
  • 30+ behavioral fixes for Matlab parity: Kalman filter, smoother, xcov, periodogram, MTMspectrum, psthGLM, GLM standard errors, FitResult plotting, and more

Key additions

  • PPSS_EMFB — Full state-space GLM EM algorithm with forward-backward smoothing
  • PPDecodeFilterLinear / PPHybridFilterLinear — Point-process adaptive and hybrid filters
  • computeHistLagForAll — History model-order sweep with logspace windows
  • SignalObj.MTMspectrum() / spectrogram() / periodogram() — Spectral methods
  • FitResSummary plotting: plotAllCoeffs, plot3dCoeffSummary, plotKSSummary

Paper examples

Example Description Figures
01 mEPSC Poisson GLM 3
02 Whisker stimulus GLM with lag/history selection 2
03 PSTH, GLM-PSTH, and SSGLM 6
04 Place cell receptive fields (Gaussian + Zernike) 7
05 Decoding: PPAF, goal-directed PPAF, hybrid filter 6

Test plan

  • 179 tests pass (1 pre-existing notebook builder failure)
  • All 5 paper examples run end-to-end with --export-figures
  • All 3 README examples run successfully
  • No regressions in existing functionality

🤖 Generated with Claude Code

Iahn Cajigas and others added 18 commits March 10, 2026 10:08
Add 19 new static methods to DecodingAlgorithms class, ported from
Matlab DecodingAlgorithms.m for v0.3.0 parity:

KF_EM family (5 methods):
- KF_EMCreateConstraints: Build constraint structures for Kalman EM
- KF_EM: Full EM algorithm for linear-Gaussian state-space models
- KF_EStep: Forward Kalman filter + RTS smoother
- KF_MStep: Parameter updates with diagonal/isotropic constraints
- KF_ComputeParamStandardErrors: Fisher information standard errors

PP_EM family (5 methods):
- PP_EMCreateConstraints: Constraint structures for point-process EM
- PP_EM: EM algorithm for point-process state-space models
- PP_EStep: Point-process forward filter + backward smoother
- PP_MStep: Newton-Raphson updates for mu/beta/gamma parameters
- PP_ComputeParamStandardErrors: Standard errors via observed information

mPPCO family (9 methods):
- mPPCODecode_predict/update: Predict-update cycle for mixed observations
- mPPCODecodeLinear: Full linear filter for mixed PP + continuous obs
- mPPCO_fixedIntervalSmoother: RTS-style smoother for mPPCO
- mPPCO_EMCreateConstraints: Constraint structures for mixed EM
- mPPCO_ComputeParamStandardErrors: Standard errors for mixed model
- mPPCO_EM/EStep/MStep: Full EM for mixed PP + continuous observations

Additional changes:
- Add _nearestSPD() helper (Higham 1988 nearest SPD matrix algorithm)
- Add _ztest_pvalue() helper for parameter significance testing
- Add FitResSummary.plotCoeffsWithoutHistory() and plotHistCoeffs()
- Add Trial.toStructure() and Trial.fromStructure() serialization
- Add validation lambda computation to Analysis.RunAnalysisForAllNeurons

All 180 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix readme examples 1-3: use correct kwargs (yunits, dataLabels)
  instead of invalid (units, labels) for SignalObj/Covariate constructors
- Rebuild all 29 Jupyter notebooks from builder scripts to pick up
  v0.3.0 method additions and code improvements
- Execute all notebooks to validate (27/29 pass; 2 pre-existing
  failures in mEPSCAnalysis and AnalysisExamples2 due to history
  matrix dimension mismatch, unrelated to v0.3.0 changes)
- Regenerate all 24 paper example figures (examples 01-05)
- Regenerate readme example images
- Regenerate parity report and Sphinx docs

All 180 tests pass. All 5 paper examples produce correct outputs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DecodingAlgorithms:
- Add computeSpikeRateCIs: Monte Carlo spike-rate CIs over time window
- Add computeSpikeRateDiffCIs: MC CIs for spike-rate difference between windows
- Remove 1018-line duplicate SSGLM block (byte-for-byte copy)
- Fix nspikeTrain constructor calls (positional arg, not keyword)

Trial:
- Add getNumHist(): return number of history window coefficients
- Add findMinSampleRate(): minimum sample rate across components
- Fix getDesignMatrix() dimension mismatch: truncate to min row count
  when covariate matrix and history matrix have off-by-one time grids

SignalObj (15 new methods):
- Label utilities: areDataLabelsEmpty, isLabelPresent, convertNamesToIndices
- Operators: __matmul__ (mtimes), ldivide, T property (transpose)
- Plot props: clearPlotProps, plotPropsSet
- Alignment: alignToMax, windowedSignal, normWindowedSignal
- Statistics: getSubSignalsWithinNStd
- Plotting: plotVariability, plotAllVariability

SpikeTrainCollection:
- Add reverseOrder parameter to plot()

Events:
- Add colorString parameter to plot()

All 179 tests pass, 2 skipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…er, docs

- Wire computeValLambda() call in analysis pipeline when validation
  partition is present (matches Matlab post-fit behaviour)
- Filter spike times to [minTime, maxTime] in computeStatistics() so
  burst metrics remain valid after setMinTime/setMaxTime
- Rewrite PPHybridFilter to evaluate CIF objects directly via
  PPDecode_update (nonlinear CIF support) instead of delegating to
  PPHybridFilterLinear with extracted linear parameters
- Add docstring to kalman_fixedIntervalSmoother documenting the
  index-extraction approximation vs Matlab's state-augmentation approach
- Fix Matplotlib deprecation: boxplot(labels=) → boxplot(tick_labels=)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Implement PPDecodeFilterLinear target estimation branch (Srinivasan
  2006): augmented state space with estimateTarget=1, and non-augmented
  feedforward term with known target yT/PiT. Ports the full Matlab
  algorithm for goal-directed decoding.

- Add FitResult.getHistCoeffsWithLabels() returning (histMat, labels,
  SEMat) tuple, matching Matlab's multi-output [histMat,labels,SEMat]=
  getHistCoeffs(). Keep getCoeffs()/getHistCoeffs() returning single
  arrays for backward compatibility; getCoeffsWithLabels() and
  getHistCoeffsWithLabels() provide the full tuple form.

- Add CovariateCollection.getCovDimension() (Matlab parity).

- Fix computeGrangerCausalityMatrix to call tObj.resetEnsCovMask()
  after completion (Matlab parity cleanup).

- Fix plotISIHistogram to honor numBins parameter when specified;
  defaults to 1ms bin width when numBins is None (matching Matlab).

- Update all internal callers of getHistCoeffs/getCoeffs to use
  appropriate variant (_rawCoeffs, getHistCoeffsWithLabels, etc.).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Matlab's nstCopy constructs the copy with makePlots=0, which triggers
computeStatistics. The Python version was passing -1, skipping stats
in the constructor. Similarly, setSigRep/setMinTime/setMaxTime were
calling computeStatistics(-1) which technically works (computes stats)
but is misleading — changed to computeStatistics(0) for clarity.

The nstcoll fixture is updated because Python's addSingleSpikeToColl
calls nstCopy() (defensive copy, unlike Matlab's reference storage),
so both trains now have valid avgFiringRate. This is correct behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Returns combined covariate + history + ensemble labels without mask
filtering, matching Matlab's Trial.getAllLabels. This differs from
the existing getLabelsFromMask(neuronNum) which applies mask filtering
and requires a neuron number.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ixes

- psthGLM now computes 1000-draw Monte Carlo confidence intervals on both
  the PSTH signal and history signal, matching the Matlab implementation.
  CIs are attached as ConfidenceInterval objects via setConfInterval().

- GLMFit now computes standard errors from the Fisher information matrix
  (Hessian inverse) and stores them in stats["se"] and stats["covb"].

- SpikeTrainCollection.plot() now accepts selectorArray, minTime, and
  maxTime parameters matching the Matlab signature.

- SpikeTrainCollection.resample() now calls setMinTime/setMaxTime on
  each train after resampling, matching Matlab behavior for time
  consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- merge() now calls makeCompatible() to auto-reconcile different time
  grids instead of raising an error (matching Matlab behavior)
- xcov() returns only non-negative lags for auto-covariance (no other
  arg), matching Matlab's positive-lags-only convention
- periodogram() loops over all signal dimensions instead of only the
  first column, matching the Matlab implementation
- MTMspectrum() adds Pval parameter (default 0.95) for chi-squared
  confidence intervals, multi-dimension support, and returns a 3-tuple
  (frequencies, psd, ci) matching Matlab's pmtm output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- normWindowedSignal: use nearest-neighbor interpolation (scipy interp1d
  kind='nearest') instead of linear (np.interp), matching Matlab's
  interp1(..., 'nearest', 0) behavior.
- getSigRep('zero-mean'): use CI-propagating operator overload (self - mu_cov)
  instead of raw NumPy subtraction, so confidence intervals are preserved
  through the zero-mean transformation (Matlab: self - self.mu).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add _kalman_filter_matlab() with full Matlab API: time-varying matrices,
  6-value return (predicted, updated, gain history, convergence iteration)
- Rewrite kalman_fixedIntervalSmoother() using exact augmented-state
  approach matching Matlab (replaces RTS approximation)
- ComputeStimulusCIs: Monte Carlo path for 4-D SSGLM covariance,
  z-score fallback for 3-D smoother covariance
- PPDecode_predict: rcond fallback matching Matlab behavior
- Preserve backward-compatible public kalman_filter() API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…action

- KSPlot: pass DTCorrection to FitResult.computeKSStats instead of discarding
- plotFitResidual: pass windowSize to FitResult.computeFitResidual
- computeHistLag: construct History objects with histMinTimes/histMaxTimes
  when provided, matching Matlab behavior
- computeGrangerCausalityMatrix: extract only the specific neighbor's
  coefficients from baseline model (fit 1) for phiMat sign computation,
  matching Matlab's strfind-based label filtering
- FitResult._compute_diagnostics: accept dt_correction parameter
- FitResult.computeFitResidual: accept windowSize parameter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- psthGLM: add missing sampleRate multiplier to GLM lambda (trial.py)
- PPSS_EMFB: use backward EM final state xKR[:,-1] for second forward pass
- PPSS_MStep: match Matlab gamma clamping (only >1e2 reduced to 1e1)
- plotSeqCorr/plotInvGausTrans: fix swap — SeqCorr shows U_j vs U_{j+1}
  scatter, InvGausTrans shows ACF of gaussianized rescaled ISIs
- plotCoeffs: add error bars (±1 SE) and significance markers
- binCoeffs: per-covariate histograms matching Matlab (returns 2-D array)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… APIs

- Rewrite example05 to use PPDecodeFilterLinear and PPHybridFilterLinear
  instead of OLS linear_decode, with 3 self-contained experiments:
  univariate sinusoidal decode, 4-D reach PPAF (free vs goal), and
  hybrid filter with discrete/continuous state estimation
- Expand examples 02-04 as self-contained documented scripts matching
  Matlab counterparts (full workflow, comments, CLI args)
- Add goal-directed backward information filter to PPHybridFilterLinear
  (was previously stubbed out)
- Upgrade FitResult.plotResults to Matlab-matching 2x4 subplot layout
  with 5 diagnostic panels (KS, InvGaus, SeqCorr, Coeffs, Residual)
- Fix plotResidual label indexing for multi-fit results
- Align all figure filenames to manifest.yml for test compatibility
- Add --repo-root CLI arg to examples 03, 04, 05

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace manual 2x2 subplot grids with plotResults() calls for
  Figures 1 and 3, giving the Matlab-matching 2x4 layout with all
  5 diagnostic panels (KS, InvGaus, SeqCorr, Coeffs, Residual)
- Fix epoch boundary off-by-one: use searchsorted(side='right') to
  match Matlab's find(time<495,1,'last') inclusive behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update examples/README.md with current paper example paths and table
- Add missing v0.3.0 methods to ClassDefinitions.md: nstColl PSTH/data
  methods, FitResSummary plotting, DecodingAlgorithms helpers, Trial.getAllLabels
- Document goal-directed decoding support in PaperOverview.md for both
  PPDecodeFilterLinear and PPHybridFilterLinear (Srinivasan et al. 2006)
- Add computeSpikeRateCIs and Monte Carlo CI details to PaperOverview.md
- Update README.md overview to mention PPAF and PPHF by name

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…psthGLM

- Replace 2D pcolormesh with 3D plot_surface for stimulus effect surfaces
- Use Poisson link (exp(x)/delta) for Figure 5 true CIF surface, matching
  Matlab's fitType='poisson' (binomial link still used for Figure 3)
- Add fresh psthGLM() call on 50-trial spikeColl for PSTH surface, matching
  Matlab flow (precomputed data only used for Figure 4 diagnostics)
- Remove unused scipy.signal dlti/TransferFunction imports from sim loop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change grid from 100x100 to 201x201 (Matlab: meshgrid(-1:0.01:1))
- Add flipud(yy)/fliplr(xx) for Matlab coordinate convention
- Use shading='gouraud' to match Matlab's shading interp

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@iahncajigas iahncajigas changed the title Port KF_EM, PP_EM, mPPCO decoding families (v0.3.0) v0.3.0: Full Matlab parity — methods, examples, and decoding algorithms Mar 11, 2026
The committed notebook had drifted from the builder output (missing
trailing newlines in cells, whitespace differences). Regenerated to
match, fixing test_network_tutorial_builder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@iahncajigas iahncajigas merged commit 18eeb83 into main Mar 11, 2026
13 of 14 checks passed
@iahncajigas iahncajigas deleted the feature/v0.3.0-missing-methods branch March 11, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant